//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package org.quickserver.net.server.impl;

import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.SocketException;
import java.nio.ByteBuffer;
import java.nio.channels.CancelledKeyException;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
import java.util.ArrayList;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.net.ssl.SSLEngineResult;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLEngineResult.HandshakeStatus;
import javax.net.ssl.SSLEngineResult.Status;
import org.quickserver.net.AppException;
import org.quickserver.net.ConnectionLostException;
import org.quickserver.net.server.AuthStatus;
import org.quickserver.net.server.ClientEvent;
import org.quickserver.net.server.ClientWriteHandler;
import org.quickserver.net.server.DataMode;
import org.quickserver.net.server.DataType;
import org.quickserver.net.server.TheClient;
import org.quickserver.util.Assertion;
import org.quickserver.util.MyString;
import org.quickserver.util.io.ByteBufferInputStream;
import org.quickserver.util.io.ByteBufferOutputStream;

public class NonBlockingClientHandler extends BasicClientHandler {
    private static final Logger logger = Logger.getLogger(NonBlockingClientHandler.class.getName());
    protected ClientWriteHandler clientWriteHandler;
    private SocketChannel socketChannel;
    protected ArrayList readByteBuffer = new ArrayList();
    protected ArrayList writeByteBuffer = new ArrayList();
    protected SelectionKey selectionKey;
    protected volatile int threadAccessCount = 0;
    protected volatile boolean willReturn;
    protected volatile boolean waitingForFinalWrite;
    private static int maxThreadAccessCount = 5;
    private static boolean wakeupSelectorAfterRegisterWrite = true;
    private static boolean wakeupSelectorAfterRegisterRead = true;
    private boolean initialHandshakeStatus = false;
    private HandshakeStatus handshakeStatus;
    private Status status = null;
    private ByteBuffer dummyByteBuffer = ByteBuffer.allocate(0);
    private ByteBuffer peerNetData = null;
    private boolean sslShutdown = false;
    private ByteBufferOutputStream byteBufferOutputStream;

    public static void setWakeupSelectorAfterRegisterWrite(boolean flag) {
        wakeupSelectorAfterRegisterWrite = flag;
    }

    public static boolean getWakeupSelectorAfterRegisterWrite() {
        return wakeupSelectorAfterRegisterWrite;
    }

    public static void setWakeupSelectorAfterRegisterRead(boolean flag) {
        wakeupSelectorAfterRegisterRead = flag;
    }

    public static boolean getWakeupSelectorAfterRegisterRead() {
        return wakeupSelectorAfterRegisterRead;
    }

    public static void setMaxThreadAccessCount(int count) {
        if (count < 3 && count != -1) {
            throw new IllegalArgumentException("Value should be >=3 or -1");
        } else {
            maxThreadAccessCount = count;
        }
    }

    public static int getMaxThreadAccessCount() {
        return maxThreadAccessCount;
    }

    public NonBlockingClientHandler(int instanceCount) {
        super(instanceCount);
    }

    public NonBlockingClientHandler() {
    }

    public void clean() {
        logger.log(Level.FINEST, "Starting clean - {0}", this.getName());
        if (this.threadAccessCount != 0) {
            logger.log(Level.WARNING, "Thread Access Count was not 0!: {0}", this.threadAccessCount);
            if (Assertion.isEnabled()) {
                this.assertionSystemExit();
            }

            this.threadAccessCount = 0;
        }

        while(!this.readByteBuffer.isEmpty()) {
            try {
                this.getServer().getByteBufferPool().returnObject(this.readByteBuffer.remove(0));
            } catch (Exception var4) {
                logger.log(Level.WARNING, "Error in returning read ByteBuffer to pool: " + var4, var4);
                break;
            }
        }

        while(!this.writeByteBuffer.isEmpty()) {
            try {
                this.getServer().getByteBufferPool().returnObject(this.writeByteBuffer.remove(0));
            } catch (Exception var3) {
                this.appLogger.log(Level.WARNING, "Error in returning write ByteBuffer to pool: " + var3, var3);
                break;
            }
        }

        if (this.peerNetData != null) {
            try {
                this.getServer().getByteBufferPool().returnObject(this.peerNetData);
            } catch (Exception var2) {
                this.appLogger.log(Level.WARNING, "Error in returning peerNetData to pool: " + var2, var2);
            }
        }

        if (this.selectionKey != null) {
            this.selectionKey.cancel();
            this.selectionKey.selector().wakeup();
            this.selectionKey = null;
        }

        this.willReturn = false;
        this.waitingForFinalWrite = false;
        this.socketChannel = null;
        if (this.byteBufferOutputStream != null) {
            this.byteBufferOutputStream.close();
        }

        super.clean();
        this.clientWriteHandler = null;
        this.byteBufferOutputStream = null;
        this.sslShutdown = false;
        logger.log(Level.FINEST, "Finished clean - {0}", this.getName());
    }

    public void handleClient(TheClient theClient) throws Exception {
        super.handleClient(theClient);
        this.setClientWriteHandler(theClient.getClientWriteHandler());
        this.setSocketChannel(theClient.getSocketChannel());
    }

    protected void setInputStream(InputStream in) throws IOException {
        this.in = in;
        if (this.getDataMode(DataType.IN) == DataMode.STRING) {
            this.b_in = null;
            this.o_in = null;
            this.bufferedReader = null;
        } else if (this.getDataMode(DataType.IN) == DataMode.OBJECT) {
            this.b_in = null;
            this.bufferedReader = null;
            this.o_in = new ObjectInputStream(in);
        } else if (this.getDataMode(DataType.IN) == DataMode.BYTE || this.getDataMode(DataType.IN) == DataMode.BINARY) {
            this.o_in = null;
            this.bufferedReader = null;
            this.b_in = null;
        }

    }

    public BufferedReader getBufferedReader() {
        throw new IllegalStateException("Access to BufferedReader in not allowed in Non-Blocking mode!");
    }

    public void closeConnection() {
        logger.finest("inside");
        synchronized(this) {
            if (!this.connection) {
                return;
            }

            if (this.waitingForFinalWrite) {
                return;
            }

            if (this.getSelectionKey() != null && this.getSelectionKey().isValid() && !this.lost) {
                this.waitingForFinalWrite = true;
            } else {
                this.connection = false;
            }
        }

        try {
            if (this.getSocketChannel() != null && this.socket != null) {
                if (this.waitingForFinalWrite) {
                    try {
                        this.waitTillFullyWritten();
                    } catch (Exception var2) {
                        logger.warning("Error in waitingForFinalWrite : " + var2);
                        if (logger.isLoggable(Level.FINE)) {
                            logger.fine("StackTrace:\n" + MyString.getStackTrace(var2));
                        }
                    }
                }

                if (this.isSecure()) {
                    this.sslShutdown = true;
                    if (!this.lost && !this.sslEngine.isOutboundDone()) {
                        logger.finest("SSL isOutboundDone is false");
                        if (!this.byteBufferOutputStream.doShutdown()) {
                            return;
                        }
                    } else if (this.sslEngine.isOutboundDone()) {
                        logger.finest("SSL Outbound is done.");
                    }
                }

                this.doPostCloseActivity();
            }
        } catch (IOException var3) {
            logger.warning("Error in closeConnection : " + var3);
            if (logger.isLoggable(Level.FINE)) {
                logger.fine("StackTrace:\n" + MyString.getStackTrace(var3));
            }
        } catch (NullPointerException var4) {
            logger.fine("NullPointerException: " + var4);
            if (logger.isLoggable(Level.FINE)) {
                logger.fine("StackTrace:\n" + MyString.getStackTrace(var4));
            }
        }

    }

    private void doPostCloseActivity() throws IOException {
        this.connection = false;
        this.byteBufferOutputStream.forceNotify();
        this.getSelectionKey().cancel();
        if (this.getServer() != null) {
            this.getServer().getSelector().wakeup();
        }

        synchronized(this) {
            if (!this.hasEvent(ClientEvent.MAX_CON)) {
                this.notifyCloseOrLost();
            }

            if (this.getSocketChannel().isOpen()) {
                logger.finest("Closing SocketChannel");
                this.getSocketChannel().close();
            }

        }
    }

    public boolean closeIfSSLOutboundDone() {
        if (!this.isSecure()) {
            throw new IllegalStateException("Client is not in secure mode!");
        } else if (this.sslEngine.isOutboundDone()) {
            logger.finest("SSL Outbound is done.");

            try {
                if (this.getSocketChannel().isOpen()) {
                    logger.finest("Closing SocketChannel");
                    this.getSocketChannel().close();
                }
            } catch (IOException var2) {
                logger.fine("IGNORE: Error in Closing SocketChannel: " + var2);
            }

            return true;
        } else {
            logger.finest("SSL Outbound is not done.");
            return false;
        }
    }

    public void waitTillFullyWritten() {
        Object waitLock = new Object();
        if (this.byteBufferOutputStream.isDataAvailableForWrite(waitLock)) {
            if (ByteBufferOutputStream.isLoggable(Level.FINEST)) {
                logger.finest("Waiting " + this.getName());
            }

            try {
                synchronized(waitLock) {
                    waitLock.wait(120000L);
                }
            } catch (InterruptedException var4) {
                logger.warning("Error: " + var4);
            }

            if (ByteBufferOutputStream.isLoggable(Level.FINEST)) {
                logger.finest("Done. " + this.getName());
            }
        }

    }

    public void run() {
        if (this.unprocessedClientEvents.isEmpty()) {
            logger.finest("No unprocessed ClientEvents!");
        } else {
            synchronized(this) {
                if (this.willReturn) {
                    return;
                }

                ++this.threadAccessCount;
            }

            ClientEvent currentEvent = (ClientEvent)this.unprocessedClientEvents.poll();
            if (currentEvent == null) {
                threadEvent.set((Object)null);
                logger.finest("No unprocessed ClientEvents! pool was null");
            } else {
                StringBuilder sb;
                if (logger.isLoggable(Level.FINEST)) {
                    sb = new StringBuilder();
                    sb.append("Running ").append(this.getName());
                    sb.append(" using ");
                    sb.append(Thread.currentThread().getName());
                    sb.append(" for ");
                    Set var3 = this.clientEvents;
                    synchronized(this.clientEvents) {
                        if (this.clientEvents.size() > 1) {
                            sb.append(currentEvent + ", Current Events - " + this.clientEvents);
                        } else {
                            sb.append(currentEvent);
                        }
                    }

                    logger.finest(sb.toString());
                }

                logger.finest("threadAccessCount: " + this.threadAccessCount);
                threadEvent.set(currentEvent);

                try {
                    if (maxThreadAccessCount != -1 && this.threadAccessCount > maxThreadAccessCount) {
                        logger.warning("ThreadAccessCount can't go beyond " + maxThreadAccessCount + ": " + this.threadAccessCount);
                        if (Assertion.isEnabled()) {
                            throw new AssertionError("ThreadAccessCount can't go beyond " + maxThreadAccessCount + ": " + this.threadAccessCount);
                        }

                        return;
                    }

                    if (this.socket == null) {
                        throw new SocketException("Socket was null!");
                    }

                    if (this.getThreadEvent() == ClientEvent.ACCEPT || this.getThreadEvent() == ClientEvent.MAX_CON) {
                        this.prepareForRun();
                        Assertion.affirm(!this.willReturn, "WillReturn has to be false!: " + this.willReturn);
                    }

                    if (this.getThreadEvent() == ClientEvent.MAX_CON) {
                        this.processMaxConnection(currentEvent);
                    }

                    try {
                        if (this.getThreadEvent() == ClientEvent.ACCEPT) {
                            this.registerForRead();
                            this.clientEventHandler.gotConnected(this);
                            if (!this.authorised) {
                                if (this.clientAuthenticationHandler == null && this.authenticator == null) {
                                    this.authorised = true;
                                    logger.finest("No Authenticator " + this.getName() + " so return thread.");
                                } else {
                                    if (this.clientAuthenticationHandler != null) {
                                        sb = null;

                                        AuthStatus authStatus;
                                        do {
                                            authStatus = this.processAuthorisation();
                                        } while(authStatus == AuthStatus.FAILURE);

                                        if (authStatus == AuthStatus.SUCCESS) {
                                            this.authorised = true;
                                        }
                                    } else {
                                        this.processAuthorisation();
                                    }

                                    if (this.authorised) {
                                        logger.finest("Authentication done " + this.getName() + ", so return thread.");
                                    } else {
                                        logger.finest("askAuthentication() done " + this.getName() + ", so return thread.");
                                    }
                                }
                            }

                            this.returnThread();
                            return;
                        }

                        if (this.connection && this.getThreadEvent() == ClientEvent.READ && this.processRead()) {
                            return;
                        }

                        if (this.connection && this.getThreadEvent() == ClientEvent.WRITE && this.processWrite()) {
                            return;
                        }
                    } catch (SocketException var8) {
                        this.appLogger.finest("SocketException - Client [" + this.getHostAddress() + "]: " + var8.getMessage());
                        this.lost = true;
                    } catch (AppException var9) {
                        this.appLogger.finest("AppException " + Thread.currentThread().getName() + ": " + var9.getMessage());
                    } catch (SSLException var10) {
                        this.lost = true;
                        if (Assertion.isEnabled()) {
                            this.appLogger.info("SSLException - Client [" + this.getHostAddress() + "] " + Thread.currentThread().getName() + ": " + var10);
                        } else {
                            this.appLogger.warning("SSLException - Client [" + this.getHostAddress() + "]: " + var10);
                        }
                    } catch (ConnectionLostException var11) {
                        this.lost = true;
                        if (var11.getMessage() != null) {
                            this.appLogger.finest("Connection lost " + Thread.currentThread().getName() + ": " + var11.getMessage());
                        } else {
                            this.appLogger.finest("Connection lost " + Thread.currentThread().getName());
                        }
                    } catch (ClosedChannelException var12) {
                        this.lost = true;
                        this.appLogger.finest("Channel closed " + Thread.currentThread().getName() + ": " + var12);
                    } catch (IOException var13) {
                        this.lost = true;
                        this.appLogger.fine("IOError " + Thread.currentThread().getName() + ": " + var13);
                    } catch (AssertionError var14) {
                        logger.warning("[AssertionError] " + this.getName() + " " + var14);
                        if (logger.isLoggable(Level.FINEST)) {
                            logger.finest("StackTrace " + Thread.currentThread().getName() + ": " + MyString.getStackTrace(var14));
                        }

                        this.assertionSystemExit();
                    } catch (RuntimeException var15) {
                        logger.warning("[RuntimeException] " + MyString.getStackTrace(var15));
                        if (Assertion.isEnabled()) {
                            this.assertionSystemExit();
                        }

                        this.lost = true;
                    } catch (Throwable var16) {
                        logger.warning("[Error] " + var16);
                        if (logger.isLoggable(Level.FINEST)) {
                            logger.finest("StackTrace " + Thread.currentThread().getName() + ": " + MyString.getStackTrace(var16));
                        }

                        if (Assertion.isEnabled()) {
                            this.assertionSystemExit();
                        }

                        this.lost = true;
                    }

                    if (this.getThreadEvent() != ClientEvent.MAX_CON) {
                        this.notifyCloseOrLost();
                    }

                    if (this.connection) {
                        logger.finest(Thread.currentThread().getName() + " calling closeConnection()");
                        this.closeConnection();
                    }

                    if (this.connection && this.lost && this.waitingForFinalWrite) {
                        this.byteBufferOutputStream.forceNotify();
                    }
                } catch (SSLException var17) {
                    logger.warning("SSLException " + Thread.currentThread().getName() + " - " + var17);
                } catch (IOException var18) {
                    logger.warning("IOError " + Thread.currentThread().getName() + " - Closing Client : " + var18);
                } catch (RuntimeException var19) {
                    logger.warning("[RuntimeException] " + this.getName() + " " + Thread.currentThread().getName() + " - " + MyString.getStackTrace(var19));
                    if (Assertion.isEnabled()) {
                        this.assertionSystemExit();
                    }
                } catch (Exception var20) {
                    logger.warning("Error " + Thread.currentThread().getName() + " - Event:" + this.getThreadEvent() + " - Socket:" + this.socket + " : " + var20);
                    logger.fine("StackTrace: " + this.getName() + "\n" + MyString.getStackTrace(var20));
                    if (Assertion.isEnabled()) {
                        this.assertionSystemExit();
                    }
                } catch (Throwable var21) {
                    logger.warning("Error " + Thread.currentThread().getName() + " - Event:" + this.getThreadEvent() + " - Socket:" + this.socket + " : " + var21);
                    logger.fine("StackTrace: " + this.getName() + "\n" + MyString.getStackTrace(var21));
                    if (Assertion.isEnabled()) {
                        this.assertionSystemExit();
                    }
                }

                synchronized(this) {
                    try {
                        if (this.getSelectionKey() != null && this.getSelectionKey().isValid()) {
                            logger.finest("Canceling SelectionKey");
                            this.getSelectionKey().cancel();
                        }

                        if (this.socket != null && !this.socket.isClosed()) {
                            logger.finest("Closing Socket");
                            this.socket.close();
                        }

                        if (this.getSocketChannel() != null && this.getSocketChannel().isOpen()) {
                            logger.finest("Closing SocketChannel");
                            this.socketChannel.close();
                        }
                    } catch (Exception var5) {
                        logger.warning("Error closing Socket/Channel: " + var5);
                    }
                }

                this.willClean = true;
                this.returnClientData();
                boolean returnClientHandler = false;
                Object var24 = this.lockObj;
                synchronized(this.lockObj) {
                    this.returnThread();
                    returnClientHandler = this.checkReturnClientHandler();
                }

                if (returnClientHandler) {
                    this.returnClientHandler();
                }

            }
        }
    }

    protected boolean checkReturnClientHandler() {
        if (!this.willReturn) {
            this.willReturn = true;
            return true;
        } else {
            return false;
        }
    }

    private boolean processRead() throws Exception {
        if (this.doRead()) {
            this.returnThread();
            return true;
        } else {
            return false;
        }
    }

    private boolean doRead() throws Exception {
        int count = 0;
        int fullCount = 0;

        do {
            try {
                if (this.peerNetData == null) {
                    this.peerNetData = (ByteBuffer)this.getServer().getByteBufferPool().borrowObject();
                }

                count = this.getSocketChannel().read(this.peerNetData);
                if (count < 0) {
                    this.getServer().getByteBufferPool().returnObject(this.peerNetData);
                    this.peerNetData = null;
                    break;
                }

                fullCount += count;
                this.peerNetData.flip();
                ByteBuffer peerAppData = null;
                if (this.sslEngine != null) {
                    peerAppData = (ByteBuffer)this.getServer().getByteBufferPool().borrowObject();

                    SSLEngineResult res;
                    do {
                        res = this.sslEngine.unwrap(this.peerNetData, peerAppData);
                        logger.info("Unwrapping:\n" + res);
                    } while(res.getStatus() == Status.OK && res.getHandshakeStatus() == HandshakeStatus.NEED_UNWRAP && res.bytesProduced() == 0);

                    if (res.getHandshakeStatus() == HandshakeStatus.FINISHED) {
                        logger.info("HandshakeStatus.FINISHED!");
                        this.finishInitialHandshake();
                    }

                    if (peerAppData.position() == 0 && res.getStatus() == Status.OK && this.peerNetData.hasRemaining()) {
                        logger.info("peerNetData hasRemaining and pos=0!");
                        res = this.sslEngine.unwrap(this.peerNetData, peerAppData);
                        logger.info("Unwrapping:\n" + res);
                    }

                    this.status = res.getStatus();
                    this.handshakeStatus = res.getHandshakeStatus();
                    if (this.status != Status.BUFFER_OVERFLOW) {
                        logger.warning("Buffer overflow: " + res.toString());
                    } else if (this.status == Status.CLOSED) {
                        logger.fine("Connection is being closed by peer.");
                        this.lost = true;
                        System.out.println("NEdd to code for shutdow of SSL");
                        break;
                    }

                    this.peerNetData.compact();
                    peerAppData.flip();
                    if (this.handshakeStatus == HandshakeStatus.NEED_TASK || this.handshakeStatus == HandshakeStatus.NEED_WRAP || this.handshakeStatus == HandshakeStatus.FINISHED) {
                        this.doHandshake();
                    }

                    logger.fine("peerAppData.remaining(): " + peerAppData.remaining());
                } else {
                    peerAppData = this.peerNetData;
                    this.peerNetData = null;
                }

                this.readByteBuffer.add(peerAppData);
                peerAppData = null;
            } catch (Exception var9) {
                logger.finest("Error in data read: " + var9);
                if (this.sslEngine != null) {
                    this.sslEngine.closeInbound();
                }

                this.lost = true;
                synchronized(this.getInputStream()) {
                    this.getInputStream().notifyAll();
                }

                throw var9;
            }
        } while(count != 0);

        if (count < 0) {
            logger.finest("SocketChannel read was " + count + "!");
            if (this.sslEngine != null) {
                this.sslEngine.closeInbound();
            }

            this.lost = true;
            synchronized(this.getInputStream()) {
                this.getInputStream().notifyAll();
            }
        } else {
            logger.finest(fullCount + " bytes read");
            if (fullCount != 0) {
                this.updateLastCommunicationTime();
                synchronized(this.getInputStream()) {
                    this.getInputStream().notify();
                }

                if (!this.hasEvent(ClientEvent.ACCEPT)) {
                    this.processGotDataInBuffers();
                }
            }

            for(; this.getInputStream().available() > 0; Thread.sleep(100L)) {
                logger.finest("Sending again for processing...");
                if (!this.hasEvent(ClientEvent.ACCEPT)) {
                    this.processGotDataInBuffers();
                    break;
                }

                synchronized(this.getInputStream()) {
                    this.getInputStream().notifyAll();
                }
            }

            if (this.connection) {
                this.registerForRead();
                return true;
            }
        }

        logger.finest("We don't have connection, lets return all resources.");
        return false;
    }

    private boolean processWrite() throws IOException {
        if (this.doWrite()) {
            this.returnThread();
            return true;
        } else {
            return false;
        }
    }

    private boolean doWrite() throws IOException {
        if (this.sslShutdown) {
            if (!this.byteBufferOutputStream.doShutdown()) {
                return true;
            } else {
                this.doPostCloseActivity();
                logger.finest("We don't have connection, lets return all resources.");
                return false;
            }
        } else {
            this.updateLastCommunicationTime();
            boolean flag = this.byteBufferOutputStream.writeAllByteBuffer();
            if (!flag) {
                this.registerWrite();
            } else if (this.clientWriteHandler != null) {
                this.clientWriteHandler.handleWrite(this);
            }

            if (this.connection) {
                return true;
            } else {
                logger.finest("We don't have connection, lets return all resources.");
                return false;
            }
        }
    }

    protected void returnThread() {
        --this.threadAccessCount;
        Assertion.affirm(this.threadAccessCount >= 0, "ThreadAccessCount went less the 0! Value: " + this.threadAccessCount);
        this.removeEvent((ClientEvent)threadEvent.get());
    }

    protected void returnClientHandler() {
        logger.finest(this.getName());

        try {
            for(int i = 0; this.threadAccessCount != 0; ++i) {
                if (i == 100) {
                    logger.warning("ClientHandler must have got into a loop waiting for thread to free up! ThreadAccessCount=" + this.threadAccessCount);
                    this.threadAccessCount = 0;
                    if (!Assertion.isEnabled()) {
                        break;
                    }

                    this.assertionSystemExit();
                }

                if (this.threadAccessCount <= 0) {
                    break;
                }

                logger.finest("Waiting for other thread of " + this.getName() + " to finish");
                Thread.sleep(60L);
            }
        } catch (InterruptedException var2) {
            this.appLogger.warning("InterruptedException: " + var2);
        }

        super.returnClientHandler();
    }

    public void setDataMode(DataMode dataMode, DataType dataType) throws IOException {
        if (this.getDataMode(dataType) != dataMode) {
            this.appLogger.fine("Setting Type:" + dataType + ", Mode:" + dataMode);
            super.checkDataModeSet(dataMode, dataType);
            this.setDataModeNonBlocking(dataMode, dataType);
        }
    }

    private void setDataModeNonBlocking(DataMode dataMode, DataType dataType) throws IOException {
        logger.finest("ENTER");
        if (dataMode == DataMode.STRING) {
            if (dataType == DataType.OUT) {
                if (this.dataModeOUT != DataMode.BYTE && this.dataModeOUT != DataMode.BINARY) {
                    if (this.dataModeOUT == DataMode.OBJECT) {
                        this.dataModeOUT = dataMode;
                        this.o_out.flush();
                        this.o_out = null;
                        this.b_out = new BufferedOutputStream(this.out);
                    } else {
                        Assertion.affirm(false, "Unknown DataType.OUT DataMode - " + this.dataModeOUT);
                    }
                } else {
                    this.dataModeOUT = dataMode;
                }

                Assertion.affirm(this.b_out != null, "BufferedOutputStream is still null!");
                Assertion.affirm(this.o_out == null, "ObjectOutputStream is still not null!");
            } else if (dataType == DataType.IN) {
                this.dataModeIN = dataMode;
                if (this.o_in != null) {
                    if (this.o_in.available() != 0) {
                        logger.warning("Data looks to be present in ObjectInputStream");
                    }

                    this.o_in = null;
                }

                this.b_in = null;
                this.bufferedReader = null;
                Assertion.affirm(this.in != null, "InputStream is still null!");
                Assertion.affirm(this.b_in == null, "BufferedInputStream is still not null!");
                Assertion.affirm(this.bufferedReader == null, "BufferedReader is still not null!");
            }
        } else if (dataMode == DataMode.OBJECT) {
            if (dataType == DataType.IN) {
                throw new IllegalArgumentException("Can't set DataType.IN mode to OBJECT when blocking mode is set as false!");
            }

            if (dataType == DataType.OUT) {
                this.dataModeOUT = dataMode;
                this.b_out = null;
                this.o_out = new ObjectOutputStream(this.out);
                Assertion.affirm(this.o_out != null, "ObjectOutputStream is still null!");
                this.o_out.flush();
            } else if (dataType == DataType.IN) {
                this.dataModeIN = dataMode;
                this.b_in = null;
                this.bufferedReader = null;
                this.registerForRead();
                this.o_in = new ObjectInputStream(this.in);
                Assertion.affirm(this.o_in != null, "ObjectInputStream is still null!");
            }
        } else {
            if (dataMode != DataMode.BYTE && dataMode != DataMode.BINARY) {
                throw new IllegalArgumentException("Unknown DataMode : " + dataMode);
            }

            if (dataType == DataType.OUT) {
                if (this.dataModeOUT != DataMode.STRING && this.dataModeOUT != DataMode.BYTE && this.dataModeOUT != DataMode.BINARY) {
                    if (this.dataModeOUT == DataMode.OBJECT) {
                        this.dataModeOUT = dataMode;
                        this.o_out = null;
                        this.b_out = new BufferedOutputStream(this.out);
                    } else {
                        Assertion.affirm(false, "Unknown DataType.OUT - DataMode: " + this.dataModeOUT);
                    }
                } else {
                    this.dataModeOUT = dataMode;
                }

                Assertion.affirm(this.b_out != null, "BufferedOutputStream is still null!");
            } else {
                if (dataType != DataType.IN) {
                    throw new IllegalArgumentException("Unknown DataType : " + dataType);
                }

                this.dataModeIN = dataMode;
                this.o_in = null;
                this.bufferedReader = null;
                this.b_in = null;
                Assertion.affirm(this.in != null, "InputStream is still null!");
            }
        }

    }

    protected byte[] readInputStream() throws IOException {
        return readInputStream(this.getInputStream());
    }

    public void updateInputOutputStreams() throws IOException {
        this.byteBufferOutputStream = new ByteBufferOutputStream(this.writeByteBuffer, this);
        this.setInputStream(new ByteBufferInputStream(this.readByteBuffer, this, this.getCharset()));
        this.setOutputStream(this.byteBufferOutputStream);
        if (this.sslEngine != null) {
            this.sslEngine.setUseClientMode(false);
            this.sslEngine.beginHandshake();
            this.handshakeStatus = this.sslEngine.getHandshakeStatus();
            this.initialHandshakeStatus = true;
        }

    }

    public boolean getBlockingMode() {
        return false;
    }

    public void setSocketChannel(SocketChannel socketChannel) {
        this.socketChannel = socketChannel;
    }

    public SocketChannel getSocketChannel() {
        return this.socketChannel;
    }

    public void setSelectionKey(SelectionKey selectionKey) {
        this.selectionKey = selectionKey;
    }

    public SelectionKey getSelectionKey() {
        if (this.selectionKey == null) {
            this.selectionKey = this.getSocketChannel().keyFor(this.getServer().getSelector());
        }

        return this.selectionKey;
    }

    private void processGotDataInBuffers() throws AppException, ConnectionLostException, ClassNotFoundException, IOException {
        if (this.getInputStream().available() != 0) {
            logger.finest("Trying to process got data.. DataMode.IN=" + this.dataModeIN);
            AuthStatus authStatus = null;
            ((ByteBufferInputStream)this.getInputStream()).dumpContent();
            String temp = null;
            String rec = null;
            Object recObject = null;
            byte[] recByte = null;
            boolean timeToCheckForNewLineMiss = false;

            do {
                if (this.dataModeIN == DataMode.STRING) {
                    ByteBufferInputStream bbin = (ByteBufferInputStream)this.getInputStream();

                    for(timeToCheckForNewLineMiss = true; bbin.isLineReady(); timeToCheckForNewLineMiss = false) {
                        rec = bbin.readLine();
                        if (rec == null) {
                            this.lost = true;
                            return;
                        }

                        if (this.getCommunicationLogging() && this.authorised) {
                            this.appLogger.log(Level.FINE, "Got STRING [{0}] : {1}", new Object[]{this.getHostAddress(), rec});
                        }

                        this.totalReadBytes += rec.length();
                        if (!this.authorised) {
                            authStatus = this.clientAuthenticationHandler.handleAuthentication(this, rec);
                        } else {
                            this.clientCommandHandler.handleCommand(this, rec);
                        }

                        if (this.isClosed()) {
                            return;
                        }

                        while(authStatus == AuthStatus.FAILURE) {
                            authStatus = this.processAuthorisation();
                        }

                        if (authStatus == AuthStatus.SUCCESS) {
                            this.authorised = true;
                        }

                        if (this.dataModeIN != DataMode.STRING) {
                            break;
                        }
                    }

                    if (timeToCheckForNewLineMiss && bbin.availableOnlyInByteBuffer() == 0) {
                        return;
                    }

                    timeToCheckForNewLineMiss = false;
                }

                while(this.dataModeIN == DataMode.OBJECT && this.o_in != null) {
                    recObject = this.o_in.readObject();
                    if (recObject == null) {
                        this.lost = true;
                        return;
                    }

                    if (this.getCommunicationLogging() && this.authorised) {
                        this.appLogger.log(Level.FINE, "Got OBJECT [{0}] : {1}", new Object[]{this.getHostAddress(), recObject.toString()});
                    }

                    ++this.totalReadBytes;
                    if (!this.authorised) {
                        authStatus = this.clientAuthenticationHandler.handleAuthentication(this, recObject);
                    } else {
                        this.clientObjectHandler.handleObject(this, recObject);
                    }

                    if (this.isClosed()) {
                        return;
                    }

                    while(authStatus == AuthStatus.FAILURE) {
                        authStatus = this.processAuthorisation();
                    }

                    if (authStatus == AuthStatus.SUCCESS) {
                        this.authorised = true;
                    }
                }

                while(this.dataModeIN == DataMode.BYTE && this.getInputStream().available() != 0) {
                    rec = this.readBytes();
                    if (rec == null) {
                        this.lost = true;
                        return;
                    }

                    if (this.getCommunicationLogging() && this.authorised) {
                        this.appLogger.log(Level.FINE, "Got BYTE [{0}] : {1}", new Object[]{this.getHostAddress(), rec});
                    }

                    this.totalReadBytes += rec.length();
                    if (!this.authorised) {
                        authStatus = this.clientAuthenticationHandler.handleAuthentication(this, rec);
                    } else {
                        this.clientCommandHandler.handleCommand(this, rec);
                    }

                    if (this.isClosed()) {
                        return;
                    }

                    while(authStatus == AuthStatus.FAILURE) {
                        authStatus = this.processAuthorisation();
                    }

                    if (authStatus == AuthStatus.SUCCESS) {
                        this.authorised = true;
                    }
                }

                while(this.dataModeIN == DataMode.BINARY && this.getInputStream().available() != 0) {
                    recByte = this.readBinary();
                    if (recByte == null) {
                        this.lost = true;
                        return;
                    }

                    if (this.getCommunicationLogging() && this.authorised) {
                        if (this.getServer().isRawCommunicationLogging()) {
                            if (this.getServer().getRawCommunicationMaxLength() > 0 && recByte.length > this.getServer().getRawCommunicationMaxLength()) {
                                this.appLogger.log(Level.FINE, "Got BINARY [{0}] : {1}; RAW: {2}{3}", new Object[]{this.getHostAddress(), MyString.getMemInfo((float)recByte.length), new String(recByte, 0, this.getServer().getRawCommunicationMaxLength(), this.charset), "..."});
                            } else {
                                this.appLogger.log(Level.FINE, "Got BINARY [{0}] : {1}; RAW: {2}", new Object[]{this.getHostAddress(), MyString.getMemInfo((float)recByte.length), new String(recByte, this.charset)});
                            }
                        } else {
                            this.appLogger.log(Level.FINE, "Got BINARY [{0}] : {1}", new Object[]{this.getHostAddress(), MyString.getMemInfo((float)recByte.length)});
                        }
                    } else if (this.getCommunicationLogging()) {
                        this.appLogger.log(Level.FINE, "Got BINARY [{0}] : {1}", new Object[]{this.getHostAddress(), MyString.getMemInfo((float)recByte.length)});
                    }

                    this.totalReadBytes += recByte.length;
                    if (!this.authorised) {
                        authStatus = this.clientAuthenticationHandler.handleAuthentication(this, recByte);
                    } else {
                        this.clientBinaryHandler.handleBinary(this, recByte);
                    }

                    if (this.isClosed()) {
                        return;
                    }

                    while(authStatus == AuthStatus.FAILURE) {
                        authStatus = this.processAuthorisation();
                    }

                    if (authStatus == AuthStatus.SUCCESS) {
                        this.authorised = true;
                    }
                }

                if (this.dataModeIN != DataMode.STRING && this.dataModeIN != DataMode.OBJECT && this.dataModeIN != DataMode.BYTE && this.dataModeIN != DataMode.BINARY) {
                    throw new IllegalStateException("Incoming DataMode is not supported : " + this.dataModeIN);
                }
            } while(this.getInputStream().available() != 0);

        }
    }

    public void registerForRead() throws IOException, ClosedChannelException {
        try {
            if (this.getSelectionKey() == null) {
                boolean flag = this.getServer().registerChannel(this.getSocketChannel(), 1, this);
                if (flag) {
                    logger.finest("Adding OP_READ as interest Ops for " + this.getName());
                } else if (ByteBufferOutputStream.isLoggable(Level.FINEST)) {
                    logger.finest("OP_READ is already present in interest Ops for " + this.getName());
                }
            } else {
                if (!this.getSelectionKey().isValid()) {
                    throw new IOException("SelectionKey is invalid!");
                }

                if ((this.getSelectionKey().interestOps() & 1) == 0) {
                    logger.finest("Adding OP_READ to interest Ops for " + this.getName());
                    this.removeEvent(ClientEvent.READ);
                    this.getSelectionKey().interestOps(this.getSelectionKey().interestOps() | 1);
                    if (wakeupSelectorAfterRegisterRead) {
                        this.getServer().getSelector().wakeup();
                    }
                } else if (ByteBufferOutputStream.isLoggable(Level.FINEST)) {
                    logger.finest("OP_READ is already present in interest Ops for " + this.getName());
                }
            }

        } catch (CancelledKeyException var2) {
            throw new IOException("SelectionKey is cancelled!");
        }
    }

    public void registerForWrite() throws IOException, ClosedChannelException {
        if (!this.hasEvent(ClientEvent.RUN_BLOCKING) && !this.hasEvent(ClientEvent.MAX_CON_BLOCKING)) {
            if (this.clientWriteHandler == null) {
                throw new IllegalStateException("ClientWriteHandler has not been set!");
            } else {
                this.registerWrite();
            }
        } else {
            throw new IllegalStateException("This method is only allowed under Non-Blocking mode.");
        }
    }

    public void registerWrite() throws IOException {
        try {
            if (this.getSelectionKey() == null) {
                boolean flag = this.getServer().registerChannel(this.getSocketChannel(), 4, this);
                if (flag) {
                    logger.finest("Adding OP_WRITE as interest Ops for " + this.getName());
                } else if (ByteBufferOutputStream.isLoggable(Level.FINEST)) {
                    logger.finest("OP_WRITE is already present in interest Ops for " + this.getName());
                }
            } else {
                if (!this.getSelectionKey().isValid()) {
                    throw new IOException("SelectionKey is invalid!");
                }

                if ((this.getSelectionKey().interestOps() & 4) == 0) {
                    logger.finest("Adding OP_WRITE to interest Ops for " + this.getName());
                    this.removeEvent(ClientEvent.WRITE);
                    this.getSelectionKey().interestOps(this.getSelectionKey().interestOps() | 4);
                    if (wakeupSelectorAfterRegisterWrite) {
                        this.getServer().getSelector().wakeup();
                    }
                } else if (ByteBufferOutputStream.isLoggable(Level.FINEST)) {
                    logger.finest("OP_WRITE is already present in interest Ops for " + this.getName());
                }
            }

        } catch (CancelledKeyException var2) {
            throw new IOException("SelectionKey is cancelled!");
        }
    }

    protected void setClientWriteHandler(ClientWriteHandler handler) {
        this.clientWriteHandler = handler;
    }

    public int getThreadAccessCount() {
        return this.threadAccessCount;
    }

    private void doHandshake() throws Exception {
        while(true) {
            logger.fine("handshakeStatus: " + this.handshakeStatus);
            if (this.handshakeStatus == HandshakeStatus.FINISHED) {
                if (this.initialHandshakeStatus) {
                    this.finishInitialHandshake();
                }

                return;
            }

            if (this.handshakeStatus == HandshakeStatus.NEED_TASK) {
                this.doTasks();
            } else {
                if (this.handshakeStatus == HandshakeStatus.NEED_UNWRAP) {
                    return;
                }

                if (this.handshakeStatus == HandshakeStatus.NEED_WRAP) {
                    ByteBuffer netData = (ByteBuffer)this.getServer().getByteBufferPool().borrowObject();
                    SSLEngineResult res = this.sslEngine.wrap(this.dummyByteBuffer, netData);
                    logger.info("Wrapping:\n" + res);

                    assert res.bytesProduced() != 0 : "No net data produced during handshake wrap.";

                    assert res.bytesConsumed() == 0 : "App data consumed during handshake wrap.";

                    this.handshakeStatus = res.getHandshakeStatus();
                    this.byteBufferOutputStream.addEncryptedByteBuffer(netData);
                    if (!this.doWrite()) {
                        return;
                    }
                } else if (this.handshakeStatus == HandshakeStatus.NOT_HANDSHAKING) {
                    assert false : "doHandshake() should never reach the NOT_HANDSHAKING state";

                    return;
                }
            }
        }
    }

    private void doTasks() {
        Runnable task;
        while((task = this.sslEngine.getDelegatedTask()) != null) {
            logger.fine("Running the task.. START ");
            task.run();
            logger.fine("Running the task.. END");
        }

        this.handshakeStatus = this.sslEngine.getHandshakeStatus();
        logger.fine("handshakeStatus: " + this.handshakeStatus);
    }

    private void finishInitialHandshake() throws IOException {
        this.initialHandshakeStatus = false;
    }

    public boolean getInitialHandshakeStatus() {
        return this.initialHandshakeStatus;
    }

    public ByteBuffer encrypt(ByteBuffer src) throws IOException {
        if (this.initialHandshakeStatus) {
            logger.fine("Writing not possible during handshake!");
            return null;
        } else {
            ByteBuffer dest = null;
            boolean isException = false;

            ByteBuffer var6;
            try {
                src.flip();
                dest = (ByteBuffer)this.getServer().getByteBufferPool().borrowObject();
                SSLEngineResult res = this.sslEngine.wrap(src, dest);
                logger.info("Wrapping:\n" + res);
                var6 = dest;
            } catch (IOException var14) {
                logger.warning("IOException:" + var14);
                isException = true;
                throw var14;
            } catch (Exception var15) {
                logger.warning("Exception:" + var15);
                isException = true;
                throw new IOException(var15.getMessage());
            } finally {
                if (isException && dest != null) {
                    try {
                        this.getServer().getByteBufferPool().returnObject(dest);
                    } catch (Exception var13) {
                        logger.warning("Error in returning ByteBuffer to pool: " + var13);
                    }
                }

            }

            return var6;
        }
    }
}
